---
title: Stack
description: Learn how to use the Stack navigator in Expo Router.
hasVideoLink: true
---

import { FileTree } from '~/ui/components/FileTree';
import { ReactNavigationOptions } from '~/ui/components/ReactNavigationOptions';
import { VideoBoxLink } from '~/ui/components/VideoBoxLink';

<VideoBoxLink
  videoId="izZv6a99Roo"
  title="Using a Stack Navigator with Expo Router"
  description="Navigate between screens, pass params between screens, create dynamic routes, and configure the screen titles and animations."
  className="mb-6"
/>

A stack navigator is the foundational way of navigating between routes in an app. On Android, a stacked route animates on top of the current screen. On iOS, a stacked route animates from the right. Expo Router provides a `Stack` navigation component that creates a navigation stack and allows you to add new routes in your app.

This guide provides information on how you can create a `Stack` navigator in your project and customize an individual route's options and header.

## Get started

You can use file-based routing to create a stack navigator. Here's an example file structure:

<FileTree files={['app/_layout.tsx', 'app/index.tsx', 'app/details.tsx']} />

This file structure produces a layout where the `index` route is the first route in the stack, and the `details` route is pushed on top of the `index` route when navigated.

You can use the **app/\_layout.tsx** file to define your app's `Stack` navigator with these two routes:

```tsx app/_layout.tsx
import { Stack } from 'expo-router';

export default function Layout() {
  return <Stack />;
}
```

## Screen options and header configuration

### Statically configure route options

You can use the `<Stack.Screen name={routeName} />` component in the layout component route to statically configure a route's options. This is also useful for [tabs](/router/advanced/tabs/) or [drawers](/router/advanced/drawer/) as they need an icon defined ahead of time.

```tsx app/_layout.tsx|collapseHeight=440
import { Stack } from 'expo-router';

export default function Layout() {
  return (
    <Stack
      /* @info <A href="https://reactnavigation.org/docs/headers/#sharing-common-options-across-screens">Click for more information on available <CODE>screenOptions</CODE> from React Navigation documentation</A>.*/
      screenOptions={{
        headerStyle: {
          backgroundColor: '#f4511e',
        },
        headerTintColor: '#fff',
        headerTitleStyle: {
          fontWeight: 'bold',
        },
      }}>
      /* @end */
      {/* Optionally configure static options outside the route.*/}
      <Stack.Screen name="home" options={{}} />
    </Stack>
  );
}
```

As an alternative to the `<Stack.Screen>` component, you can use [`navigation.setOptions()`](https://reactnavigation.org/docs/navigation-object/#setoptions) to configure a route's options from within the route's component file.

```tsx app/index.tsx
import { Stack, useNavigation } from 'expo-router';
import { Text, View } from 'react-native';
import { useEffect } from 'react';

export default function Home() {
  const navigation = useNavigation();

  useEffect(() => {
    navigation.setOptions({ headerShown: false });
  }, [navigation]);

  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>Home Screen</Text>
    </View>
  );
}
```

### Configure header bar

You can configure the header bar for all routes in a `Stack` navigator by using the `screenOptions` prop. This is useful for setting a common header style across all routes.

```tsx app/_layout.tsx
import { Stack } from 'expo-router';

export default function Layout() {
  return (
    <Stack
      screenOptions={{
        headerStyle: {
          backgroundColor: '#f4511e',
        },
        headerTintColor: '#fff',
        headerTitleStyle: {
          fontWeight: 'bold',
        },
      }}
    />
  );
}
```

To configure the header bar dynamically for an individual route, use that navigator's `<Stack.Screen>` component in the routes's file. This is useful for interactions that change the UI.

```tsx app/index.tsx
import { Link, Stack } from 'expo-router';
import { Image, Text, View, StyleSheet } from 'react-native';

function LogoTitle() {
  return (
    <Image style={styles.image} source={{ uri: 'https://reactnative.dev/img/tiny_logo.png' }} />
  );
}

export default function Home() {
  return (
    <View style={styles.container}>
      <Stack.Screen
        options={{
          /* @info <A href="https://reactnavigation.org/docs/headers/#adjusting-header-stylesheader-styles">Click for more information on adjusting header styles from React Navigation documentation</A>.*/
          title: 'My home',
          headerStyle: { backgroundColor: '#f4511e' },
          headerTintColor: '#fff',
          headerTitleStyle: {
            fontWeight: 'bold',
          },
          /* @end */

          /* @info <A href="https://reactnavigation.org/docs/headers/#replacing-the-title-with-a-custom-component">Click for more information on replacing a title with a custom component from React Navigation documentation</A>.*/
          headerTitle: props => <LogoTitle {...props} />,
          /* @end */
        }}
      />
      <Text>Home Screen</Text>
      <Link href={{ pathname: 'details', params: { name: 'Bacon' } }}>Go to Details</Link>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  image: {
    width: 50,
    height: 50,
  },
});
```

### Available header options

The `Stack` navigator supports comprehensive header configuration options. Below are all the header-related options available:

<ReactNavigationOptions category="header" />

For additional details and navigator-specific examples, see [React Navigation's Native Stack Navigator documentation](https://reactnavigation.org/docs/native-stack-navigator).

### Set screen options dynamically

To configure a route's option dynamically, you can always use the `<Stack.Screen>` component in that route's file.

As an alternative, you can also use the [imperative API's `router.setParams()`](/versions/latest/sdk/router/#router) function to configure the route dynamically.

```tsx app/details.tsx
import { Stack, useLocalSearchParams, useRouter } from 'expo-router';
import { View, Text, StyleSheet } from 'react-native';

export default function Details() {
  const router = useRouter();
  const params = useLocalSearchParams();

  return (
    <View style={styles.container}>
      <Stack.Screen
        options={{
          title: params.name,
        }}
      />
      <Text
        onPress={() => {
          router.setParams({ name: 'Updated' });
        }}>
        Update the title
      </Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
});
```

### Header buttons

You can add buttons to the header by using the `headerLeft` and `headerRight` options. These options accept a React component that renders in the header.

```tsx app/index.tsx
import { Stack } from 'expo-router';
import { Button, Text, Image, StyleSheet } from 'react-native';
import { useState } from 'react';

function LogoTitle() {
  return (
    <Image style={styles.image} source={{ uri: 'https://reactnative.dev/img/tiny_logo.png' }} />
  );
}

export default function Home() {
  const [count, setCount] = useState(0);

  return (
    <>
      <Stack.Screen
        options={{
          headerTitle: props => <LogoTitle {...props} />,
          headerRight: () => <Button onPress={() => setCount(c => c + 1)} title="Update count" />,
        }}
      />
      <Text>Count: {count}</Text>
    </>
  );
}

const styles = StyleSheet.create({
  image: {
    width: 50,
    height: 50,
  },
});
```

### Other screen options

For a complete list of all available other screen options including animations, gestures, and other configurations:

<ReactNavigationOptions excludeCategories={['header']} />

For additional details and navigator-specific examples, see [React Navigation's Native Stack Navigator documentation](https://reactnavigation.org/docs/native-stack-navigator).

## Custom push behavior

By default, the `Stack` navigator removes duplicate screens when pushing a route that is already in the stack. For example, if you push the same screen twice, the second push will be ignored. You can change this push behavior by providing a custom `getId()` function to the `<Stack.Screen>`.

For example, the `index` route in the following layout structure shows a list of different user profiles in the app. Let's make the `[details]` route a [dynamic route](/router/basics/notation/#square-brackets) so that the app user can navigate to see a profile's details.

<FileTree
  files={[
    'app/_layout.tsx',
    'app/index.tsx',
    ['app/[details].tsx', "matches dynamic paths like '/details1'"],
  ]}
/>

The `Stack` navigator will push a new screen every time the app user navigates to a different profile but will fail. If you provide a `getId()` function that returns a new ID every time, the `Stack` will push a new screen every time the app user navigates to a profile.

You can use the `<Stack.Screen name="[profile]" getId={}>` component in the layout component route to modify the push behavior:

```tsx app/_layout.tsx
import { Stack } from 'expo-router';

export default function Layout() {
  return (
    <Stack>
      <Stack.Screen
        name="[profile]"
        getId={
          /* @info Returning a new ID every time will cause every page to push. */
          ({ params }) => String(Date.now())
          /* @end */
        }
      />
    </Stack>
  );
}
```

## Removing stack screens

There are different actions you can use to dismiss and remove one or many routes from a stack.

### `dismiss` action

Dismisses the last screen in the closest stack. If the current screen is the only route in the stack, it will dismiss the entire stack.

You can optionally pass a positive number to dismiss up to that specified number of screens.

Dismiss is different from `back` as it targets the closest stack and not the current navigator. If you have nested navigators, calling `dismiss` will take you back multiple screens.

{/* prettier-ignore */}
```tsx app/settings.tsx
import { Button, View } from 'react-native';
/* @info Import <CODE>useRouter</CODE> from Expo Router. */
import { useRouter } from 'expo-router';
/* @end */

export default function Settings() {
  /* @info Access the router from the <CODE>useRouter</CODE> hook. */
  const router = useRouter();
  /* @end */

  const handleDismiss = (count: number) => {
    /* @info In the handler method, call <CODE>router.dismiss</CODE> to go back. */
    router.dismiss(count)
    /* @end */
  };

  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      /* @info Trigger the handler method on a touchable or button component. */
      <Button title="Go to first screen" onPress={() => handleDismiss(3)} />
      /* @end */
    </View>
  );
}
```

### `dismissTo` action

> `dismissTo` was added in Expo Router `4.0.8`. It operates similarly to the `navigation` function in Expo Router v3.

Dismisses screens in the current `<Stack />` until the specified `Href` is reached. If the `Href` is absent in the history, a `push` action will be performed instead.

For example, consider the history of `/one`, `/two`, `/three` routes, where `/three` is the current route. The action `router.dismissTo('/one')` will cause the history to go back twice, while `router.dismissTo('/four')` will `push` the history forward to the `/four` route.

{/* prettier-ignore */}
```tsx app/settings.tsx
import { Button, View, Text } from 'react-native';
/* @info Import <CODE>useRouter</CODE> from Expo Router. */
import { useRouter } from 'expo-router';
/* @end */

export default function Settings() {
  /* @info Access the router from the <CODE>useRouter</CODE> hook. */
  const router = useRouter();
  /* @end */

  const handleDismissAll = () => {
    /* @info In the handler method, call <CODE>router.dismissTo</CODE> to go back to a specific route. */
    router.dismissTo('/')
    /* @end */
  };

  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      /* @info Trigger the handler method on a touchable or button component. */
      <Button title="Go to first screen" onPress={handleDismissAll} />
      /* @end */
    </View>
  );
}
```

### `dismissAll` action

To return to the first screen in the closest stack. This is similar to [`popToTop`](https://reactnavigation.org/docs/stack-actions/#poptotop) stack action.

For example, the `home` route is the first screen, and the `settings` is the last. To go from `settings` to `home` route you'll have to go back to `details`. However, using the `dismissAll` action, you can go from `settings` to `home` and dismiss any screen in between.

{/* prettier-ignore */}
```tsx app/settings.tsx
import { Button, View, Text } from 'react-native';
/* @info Import <CODE>useRouter</CODE> from Expo Router. */
import { useRouter } from 'expo-router';
/* @end */

export default function Settings() {
  /* @info Access the router from the <CODE>useRouter</CODE> hook. */
  const router = useRouter();
  /* @end */

  const handleDismissAll = () => {
    /* @info In the handler method, call <CODE>router.dismissAll</CODE> to go back to the first screen in a stack. */
    router.dismissAll()
    /* @end */
  };

  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      /* @info Trigger the handler method on a touchable or button component. */
      <Button title="Go to first screen" onPress={handleDismissAll} />
      /* @end */
    </View>
  );
}
```

### `canDismiss` action

To check if it is possible to dismiss the current screen. Returns `true` if the router is within a stack with more than one screen in the stack's history.

{/* prettier-ignore */}
```tsx app/settings.tsx|collapseHeight=410
import { Button, View } from 'react-native';
/* @info Import <CODE>useRouter</CODE> from Expo Router. */
import { useRouter } from 'expo-router';
/* @end */

export default function Settings() {
  /* @info Access the router from <CODE>useRouter</CODE> hook. */
  const router = useRouter();
  /* @end */

  const handleDismiss = (count: number) => {
    /* @info Check if we can dismiss. */
    if (router.canDismiss()) {
      /* @info In the handler method, call <CODE>router.dismiss</CODE> to go back to. */
      router.dismiss(count)
      /* @end */
    }
    /* @end */
  };

  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      /* @info Trigger the handler method on a touchable or button component. */
      <Button title="Maybe dismiss" onPress={() => handleDismiss()} />
      /* @end */
    </View>
  );
}
```

## Relation with Native Stack Navigator

The `Stack` navigator in Expo Router wraps the [Native Stack Navigator](https://reactnavigation.org/docs/native-stack-navigator) from React Navigation. Options available in the Native Stack Navigator are all available in the `Stack` navigator in Expo Router.

### JavaScript stack with @react-navigation/stack

You can also use the JavaScript-powered `@react-navigation/stack` library to create a custom layout component by wrapping this library with the `withLayoutContext`.

In the following example, `JsStack` component is defined using `@react-navigation/stack` library:

```tsx layouts/js-stack.tsx
import { ParamListBase, StackNavigationState } from '@react-navigation/native';
import {
  createStackNavigator,
  StackNavigationEventMap,
  StackNavigationOptions,
} from '@react-navigation/stack';
import { withLayoutContext } from 'expo-router';

const { Navigator } = createStackNavigator();

export const JsStack = withLayoutContext<
  StackNavigationOptions,
  typeof Navigator,
  StackNavigationState<ParamListBase>,
  StackNavigationEventMap
>(Navigator);
```

After defining the `JsStack` component, you can use it in your app:

{/* prettier-ignore */}
```tsx app/_layout.tsx
import { JsStack } from '../layouts/js-stack';

export default function Layout() {
  return (
    <JsStack
      screenOptions={
        {
          /* @hide ... */
          /* @end */
        }
      }
    />
  );
}
```

For more information on available options, see [`@react-navigation/stack` documentation](https://reactnavigation.org/docs/stack-navigator).

## iOS 26 Liquid Glass headers

Starting from iOS 26, navigation headers adopt the system's "Liquid Glass" effect by default. It cannot be disabled per screen, so you need to opt out using a global configuration.

### Method 1: Use `UIDesignRequiresCompatibility`

> **Note**: Not supported in Expo Go.This method is a temporary workaround. From iOS 27, this option will be removed by Apple and you cannot opt out of the Liquid Glass effect.

Create a [development build](/develop/development-builds/create-a-build/#prerequisites) and set the [`UIDesignRequiresCompatibility`](https://developer.apple.com/documentation/BundleResources/Information-Property-List/UIDesignRequiresCompatibility) property to `true` in [app config](/workflow/configuration/):

```json app.json
{
  "ios": {
    "infoPlist": {
      "UIDesignRequiresCompatibility": true
    }
  }
}
```

### Method 2: Use JavaScript-based navigation stack

Switch from native navigation library ([`@react-navigation/native`](https://reactnavigation.org/docs/native-stack-navigator/)) to a JavaScript-based stack navigator library such as [`@react-navigation/stack`](https://reactnavigation.org/docs/stack-navigator/), which gives you full control over the header UI but at the cost of performance benefits of using the highly optimized iOS navigation views/controllers.

For more information, see [JavaScript stack with `@react-navigation/stack`](#javascript-stack-with-react-navigationstack).
